#include "SkyboxRenderPass.h"
#include <SERender/Resourses/Loader/TextureLoader.h>
#include <SERender/Renderer/Renderer.h>
#include <SEngine/Render/RenderFeature/SkyBoxRenderFeature.h>
#include <SEngine/Render/RenderFeature/UtilRenderFeature.h>
#include <SEngine/Render/Descriptors/SceneDescriptor.h>
#include <SECore/Ecs/Scene.h>
#include <SEngine/Ecs/Components/ModelRenderComp.h>
#include <SERender/Resourses/Shader.h>

#include <SERender/Renderer/RenderTask.h>
#include <SERender/Entity/Camera.h>
#include <SERender/Descriptors/FrameDescriptor.h>
#include <SECore/Manager.h>
#include <SERender/Resourses/Shader.h>
#include <SECore/CoreApplication.h>
#include <SEUi/Util/Util.h>

#include <imgui.h>
#include <SERender/Resourses/Loader/ShaderLoader.h>
#include <SEngine/Render/RenderFeature/UBORenderFeature.h>
#include <SERender/Resourses/Loader/TextureLoader.h>
#include <SEngine/Render/Descriptors/SceneDescriptor.h>
#include <SEngine/Ecs/Components/HDRSkyboxComp.h>
#include <SEngine/Ecs/Components/DynamicSkyboxComp.h>

namespace SEngine
{
    constexpr int IrradianceMapSize = 32;
    constexpr int PreFilterMapSize = 128;

    SkyboxRenderPass::SkyboxRenderPass(Renderer &renderer)
        : RenderPass(renderer)
    {
        m_fbo = std::make_unique<FrameBuffer>();
    }

    void SkyboxRenderPass::BeginFrame()
    {
        auto frameDescriptor = m_renderer.AnyDescriptor::GetDescriptor<FrameDescriptor>();
        auto & skyboxFeature = m_renderer.Descriptor<RenderFeature>::GetDescriptor<SkyBoxRenderFeature>()->get();
        auto & utilFeature = m_renderer.Descriptor<RenderFeature>::GetDescriptor<UtilRenderFeature>()->get();
        auto scene = m_renderer.AnyDescriptor::GetDescriptor<SceneDescriptor>()->get().scene;
        if (!frameDescriptor) return;   
        scene->Components<HDRSkyboxComp>([this, frameDescriptor, &skyboxFeature, &utilFeature](Actor & actor, HDRSkyboxComp & comp) {
            if (comp.skybox && m_textures.textureID != comp.skybox.Value().GetId()) {
                m_textures.textureID = comp.skybox.Value().GetId();
                TextureLoader<TextureCube>  loaderCube;
                loaderCube.SetInternalFormat(RGB16F);
                loaderCube.SetFormat(RGB);
                loaderCube.SetType(FLOAT);
                loaderCube.SetParameter(TEXTURE_WRAP_S, CLAMP_TO_EDGE);
                loaderCube.SetParameter(TEXTURE_WRAP_T, CLAMP_TO_EDGE);
                loaderCube.SetParameter(TEXTURE_WRAP_R, CLAMP_TO_EDGE);
                loaderCube.SetParameter(TEXTURE_MIN_FILTER, LINEAR);
                loaderCube.SetParameter(TEXTURE_MAG_FILTER, LINEAR);
                m_textures.irradianceMap = loaderCube.CreateTexture(IrradianceMapSize, IrradianceMapSize, nullptr);
    
                loaderCube.SetParameter(TEXTURE_MIN_FILTER, LINEAR_MIPMAP_LINEAR);
                loaderCube.SetLevel(5); // 设置mipmap等级
                m_textures.preFilterMap = loaderCube.CreateTexture(PreFilterMapSize, PreFilterMapSize, nullptr);
    
                m_renderer.UseFrameBuffer(*m_fbo, [this, frameDescriptor, &skyboxFeature, &skybox = comp.skybox, &utilFeature]() {
                    glm::mat4 captureProjection = glm::perspective(glm::radians(90.0f), 1.0f, 0.1f, 10.0f);
                    glm::mat4 captureViews[] = {
                        glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 1.0f,  0.0f,  0.0f), glm::vec3(0.0f, -1.0f,  0.0f)),
                        glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(-1.0f,  0.0f,  0.0f), glm::vec3(0.0f, -1.0f,  0.0f)),
                        glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f,  1.0f,  0.0f), glm::vec3(0.0f,  0.0f,  1.0f)),
                        glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f, -1.0f,  0.0f), glm::vec3(0.0f,  0.0f, -1.0f)),
                        glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f,  0.0f,  1.0f), glm::vec3(0.0f, -1.0f,  0.0f)),
                        glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f,  0.0f, -1.0f), glm::vec3(0.0f, -1.0f,  0.0f))
                    };
                    
                    // 间接漫反射辐照度
                    m_renderer.GetDrive()->Viewport(0, 0, IrradianceMapSize, IrradianceMapSize);
                    auto envConvMaterial = Manager::GetInstance().Get<Material>("EnvConv").value();
                    envConvMaterial.Value().Set("project", captureProjection);
                    for (int i = 0; i < 6; ++i) {
                        envConvMaterial.Value().Set("view", captureViews[i]);
                        m_fbo->SwapAttachments(COLOR_ATTACHMENT0, *m_textures.irradianceMap, TEXTURE_CUBE_MAP_POSITIVE_X + i);
                        m_fbo->Use();
                        m_renderer.GetDrive()->Clear(COLOR_BUFFER_BIT | DEPTH_BUFFER_BIT);
                        skyboxFeature.DrawSkyBox(skybox.Value(), envConvMaterial);
                    }
                    m_fbo->ClearAttachments();
                    
                    // 预滤波环境贴图
                    auto preFilterEnvMaterial = Manager::GetInstance().Get<Material>("PreFilterEnv").value();
                    preFilterEnvMaterial.Value().Set("project", captureProjection);
                    unsigned int maxMipLevels = 5;
                    for (unsigned int mip = 0; mip < maxMipLevels; ++mip) {
                        unsigned int mipWidth  = PreFilterMapSize * std::pow(0.5, mip);
                        unsigned int mipHeight = PreFilterMapSize * std::pow(0.5, mip);
                        float roughness = (float)mip / (float)(maxMipLevels - 1);
                        preFilterEnvMaterial.Value().Set("u_roughness", roughness);
                        m_renderer.GetDrive()->Viewport(0, 0, mipWidth, mipHeight);
                        for (int i = 0; i < 6; ++i) {
                            preFilterEnvMaterial.Value().Set("view", captureViews[i]);
                            m_fbo->SwapAttachments(COLOR_ATTACHMENT0, *m_textures.preFilterMap, TEXTURE_CUBE_MAP_POSITIVE_X + i, mip);
                            m_fbo->Use();
                            m_renderer.GetDrive()->Clear(COLOR_BUFFER_BIT | DEPTH_BUFFER_BIT);
                            skyboxFeature.DrawSkyBox(skybox.Value(), preFilterEnvMaterial);
                        }
                    }
                    m_fbo->ClearAttachments();
    
                    m_renderer.GetDrive()->Viewport(
                        frameDescriptor->get().x,
                        frameDescriptor->get().y,
                        frameDescriptor->get().w,
                        frameDescriptor->get().h
                    );
                });
            }
            
            // 给所有材质设置天空盒贴图
            Manager::GetInstance().Foreach<Material>([&skybox = comp.skybox, this](ResRef<Material> & material) {
                material.Value().Set("u_irradianceMap", Shader::Sampler{ !skybox ? *TextureCube::GetDefaultTexture() : *m_textures.irradianceMap, TextureSlot::TEXTURE0});
                material.Value().Set("u_prefilterMap", Shader::Sampler{ !skybox ? *TextureCube::GetDefaultTexture() : *m_textures.preFilterMap, TextureSlot::TEXTURE1});
                material.Value().Set("u_brdfLUT", Shader::Sampler{ !skybox ? *Texture2D::GetDefaultTexture() : Manager::GetInstance().Get<Texture>("BRDF_LUT").value().Value(), TextureSlot::TEXTURE2});
            });
            return false;
        });
    }

    void SkyboxRenderPass::DrawFrame()
    {
        auto frameDescriptor = m_renderer.AnyDescriptor::GetDescriptor<FrameDescriptor>();
        if (!frameDescriptor) return;
        auto & skyboxFeature = m_renderer.Descriptor<RenderFeature>::GetDescriptor<SkyBoxRenderFeature>()->get();
        auto scene = m_renderer.AnyDescriptor::GetDescriptor<SceneDescriptor>()->get().scene;

        scene->Components<DynamicSkyboxComp>([&skyboxFeature](Actor & actor, DynamicSkyboxComp & comp) {
            skyboxFeature.DrawSkyBox(comp.skybox);
            return false;
        });

        scene->Components<HDRSkyboxComp>([this, frameDescriptor, &skyboxFeature](Actor & actor, HDRSkyboxComp & comp) {
            if (comp.skybox) {
                skyboxFeature.DrawSkyBox(comp.skybox.Value());
            }
            return false;   
        });
    }

} // namespace SEngine
